#include "decl.h"


struct decl * decl_create ( 
  char * name, 
  int nlocals, 
  struct type * type, 
  struct expr * value, 
  struct stmt * code, 
  struct symbol * symbol, 
  struct decl * next)
{
  struct decl *a = malloc(sizeof(*a));

	a->name  = name;
	a->nlocals = nlocals;
	a->type  = type;
	a->value = value;
	a->code = code;
	a->symbol = symbol;
	a->next  = next;

	return a;
}


void decl_print( struct decl * d)
{
  if (!d) return;

  if (d->type)
    type_print(d->type);

  if (d->name)
    //    expr_print(d->name);
    printf(d->name);

  if (d->value)
  {
    printf (" = ");
    expr_print(d->value);
  }

  if (d->type->kind == TYPE_KIND_FUNCTION)
  {
    printf("(");
    param_list_print(d->type->params);
    printf(")");
    if (d->code)
    {
      stmt_print(d->code);
    }
    else printf(";");
  }
  else printf(";");

  printf("\n");

  decl_print(d->next);
}

void decl_resolve(struct decl * d, int print)
{
  if (!d) return;
  struct symbol * a;
  
  int kind = (scope_level() == 0) ? SYMBOL_KIND_GLOBAL : SYMBOL_KIND_LOCAL;
  //printf("d->type->kind: %d; kind: %d\n", d->type->kind, kind);
  if (d->type->kind == TYPE_KIND_FUNCTION && kind == SYMBOL_KIND_LOCAL)
  {
    printf("error: you are trying to define the function '%s' \n inside another function. \
This is not allowed.\n", d->name);
    exit(1);
  }
  else if (d->type->kind == TYPE_KIND_FUNCTION)
  {
    if (d->code)    //is not a prototype
      a = symbol_create(kind, local_count, d->type, d->name, 1);
    else            //is a prototype
      a = symbol_create(kind, local_count, d->type, d->name, 0);
  
    int notInTable = scope_bind(d->name, a);  // =0 if in table already

    struct symbol * s;
    if (!notInTable) //handles a previous entry in symbol table
    {
      s = scope_lookup(d->name);
      if (s->isDefined && a->isDefined)  //two implementations
      {
        printf("error: you are trying to implement the function '%s' in two places.\n", d->name);
        exit(1);
      } else if (s->isDefined && s->type->kind != TYPE_KIND_FUNCTION) 
      {
	printf("error: variable '%s' already declared.\n", d->name);
	exit(1);
      }
      s->isDefined =  (s->isDefined || a->isDefined);
      d->symbol = s;
    }
  } else  {
    a = symbol_create(kind, local_count++, d->type, d->name, 1);
    int notInTable = scope_bind(d->name, a);
    if (!notInTable) {
      printf("error: multiple declarations for the variable %s\n", d->name);
      exit(1);
    }
  }
  

  if (d->value)
  {
    expr_resolve(d->value, print);
  }
  //if anything other than function:  


  if (d->code)
  {
    scope_enter();
    param_list_resolve(d->type->params, 0);
    stmt_resolve(d->code, print);
    scope_exit();
    d->nlocals = local_count; //global var todo
    local_count = 0;
  }
  decl_resolve(d->next, print);
  
}

void decl_typecheck(struct decl * d)
{
  if (!d) return;
  if (d->code)  stmt_typecheck(d->code, d->type);
  if (d->value)
  {
    struct type * etype = expr_typecheck(d->value);
    if (!type_compare(d->type, etype ) )
    {
      printf("error: declaring a variable one type and then assigning another type\n");
      printf("declared: "); type_print(d->type); printf("\n");
      printf("assigned: "); type_print(etype);   printf("\n");
      exit(1);  
    }
  } 
  struct type * VOIDIE = type_create(TYPE_KIND_VOID, 0, 0);
  if (type_compare(d->type, VOIDIE) && d->type->kind != TYPE_KIND_FUNCTION)
  {
    printf("error: void variables not allowed\n");
    exit(1);
  }
  decl_typecheck(d->next);
}

void decl_codegen(FILE * file, struct decl * d)
{
  if (!d) return;
  if (d->symbol->kind == SYMBOL_KIND_GLOBAL)
  {
    if (d->type->kind == TYPE_KIND_FUNCTION)
    { 
      fprintf(file, ".text\n");
      fprintf(file, ".globl %s\n", d->name);
      fprintf(file, "%s:\n", d->name);
      fprintf(file, "  push %%ebp\n");
      fprintf(file, "  mov %%esp, %%ebp\n");
      fprintf(file, "  sub $(%d), %%esp\n", 4*d->nlocals);
      fprintf(file, "  push %%ebx\n");
      fprintf(file, "  push %%ecx\n");
      fprintf(file, "  push %%edx\n");
      fprintf(file, "  push %%esi\n")
      fprintf(file, "  push %%edi\n");
      stmt_codegen(d->code);
      fprintf(file, ".name_return:\n");
      fprintf(file, "  pop   %%edi\n");
      fprintf(file, "  pop   %%esi\n");
      fprintf(file, "  pop   %%edx\n");
      fprintf(file, "  pop   %%ecx\n")
      fprintf(file, "  pop   %%ebx\n");
      fprintf(file, "  mov %%ebp, %%esp\n")
      fprintf(file, "  pop %%ebp\n");
      fprintF(file, "  ret\n");
    } else
    { 
      expr_codegen_constant(d->expr)
    }
    
  }
  else {  //locals
    expr_codegen(d->expr);
    fprintf(file, "mov %%%s, %%%s\n", register_name(d->expr->reg), symbol_code(d->symbol);
    register_free(d->expr->reg);
  
}
